; NASM 汇编编译器为 bin 文件格式提供了额外的操作符 org
; 它的功能是指定程序被载入内存时的起始地址
; 一句话，就是为程序中所有的内部地址引用增加一个段内偏移值
; 引导程序可以看做是被加载到以 0 为基址的段，偏移为 0x7c00
org 0x7c00

jmp start			; 跳转到 start 标签处运行

start:
    xor ax, ax		; xor 指令与 and or 指令类似，两个操作数的每一对对应位都应用如下操作原则：
					; 如果两个位的值相同（同为 0 或同为 1），则结果位等于 0；否则结果位等于 1
					; xor ax, ax 等于 ax = 0
	mov ss, ax		; 栈段 ss = 0
	mov ds, ax		; 数据段 ds = 0
	mov es, ax		; 附加数据段 es = 0 
	mov sp, 0x7c00	; 设置栈

	; 功能：清屏
	mov ah, 0x06    ; AH 功能号 = 0x06
	mov bh, 0x07    ; 清屏属性
	mov al, 0x00    ; AL = 上卷的行数（如果为0，表示全部）
	mov cx, 0x0000  ; 窗口左上角(CL, CH)位置,即(0, 0)
	mov dx, 0x184f  ; 窗口右下角(DL, DH)位置
					; VGA 文本模式中，一行只能容纳 80 个字节，共 25 行 ，
					; 下标从 0 开始，所以 0x18 =24, 0x4f = 79
	int 0x10

	; 设置光标位置 (dl, dh)
	mov ah, 0x02    ; AH 功能号 = 0x02，设置光标
	mov dh, 0x00    ; 设置光标行号
	mov dl, 0x00    ; 设置光标列号
	mov bh, 0x00    ; 页码
	int 0x10

	; 打印 msg
	mov ax, msg
	mov cx, msgLen
	call print

	; 将硬盘扇区 2 中的数据读入到内存  0x900 处
	mov eax, 0x02
	mov bx, 0x900
    mov cx, 20
	call rd_disk_to_mem
	jmp 0x900		; 现在 loader 还没实现，不能跳转过去

	jmp $			; 死循环. $ 表示程序当前位置
	

; 显示字符串
; ax : 输入参数，字符串地址
; cx : 输入参数，字符串长度
print:
	; 入栈
	push ax
	push bp
	push bx

	mov bp, ax      ; 保存字符串地址
	mov ax, 0x1301  ; 子功能号 0x13 是写字符串
	mov bx, 000fh   ; 页号位 0，黑底白字
	int 0x10  

	; 出栈
	pop bx
	pop bp
	pop ax

	ret    

; 功能：读取硬盘扇区并将其写入内存
; eax : 输入参数， 扇区号
; bx : 输入参数，将数据写入的内存地址
; cx : 输入参数，读取扇区数
rd_disk_to_mem:
	mov esi, eax	; 备份 eax
	mov di, cx		; 备份 cx
	
	; 第 1 步：设置要读取的扇区数
	mov dx, 0x1f2
	mov al, cl   
	out dx, al		; 读取的扇区数

	mov eax, esi	; 恢复 eax

	; 第 2 步：将 LBA 地址存入 0x1f3 ~ 0x1f6

	; LBA 地址 7 ~ 0 位写入端口 0x1f3
	mov dx, 0x1f3
	out dx, al

	; LBA 地址 15 ~ 8 位写入端口 0x1f4
	mov cl, 8
	shr eax, cl
	mov dx, 0x1f4
	out dx, al

	;  LBA 地址 23 ~ 16 位写入端口 0x1f5
	shr eax, cl
	mov dx, 0x1f5
	out dx, al

	shr eax, cl
	and al, 0x0f 	; LBA 第 24 ~ 27 位
	or al, 0xe0 	; 设置7 ~4 位为 1110,表示 LBA 模式
	mov dx, 0x1f6
	out dx, al

	; 第 3 步：向 0x1f7 端口写入读命令， 0x20
	mov dx, 0x1f7
	mov al, 0x20
	out dx, al

	; 第 4 步：检测硬盘状态
.not_ready:
	; 同一端口，写时表示写入命令字，读时表示读入硬盘状态
	nop
	in al, dx
	and al, 0x88	; 第 4 位为 1 表示硬盘控制权已准备好数据传输，
					; 第 7 位为 1 表示硬盘忙
	cmp al, 0x08
	jnz .not_ready	; 若未准备好，继续等

	; 第 5 步：从 0x1f0 端口读数据
	mov ax, di
	; di 为要读取的扇区数,一个扇区有512 字节,每次读入一个字
	; 共需di*512/2 次,所以di*256
	mov dx, 256
	mul dx			
	mov cx, ax
	mov dx, 0x1f0
.go_on_read:
	in ax, dx
	mov [bx], ax
	add bx, 2
	loop .go_on_read

	ret

msg db "Welcome to KOS."	; 使用 db 定义数据
msgLen equ $-msg			; 字符串 msg 长度

times 510-($-$$) db 0x00 	; $ 表示程序当前位置
							; $$ 指代本 section 的起始地址
							; 如果没有定义 section ，nasm 默认全部代码同为一个 section ，起始地址为 0
							; 该指令表示将当前位置到 510 字节处全部填充数据 0x00
db 0x55, 0xaa			 	; 主引导程序最后 2 个字节为 0x55, 0xaa